/* defines template javascript class

Author: Wilco
*/

function Template(template) {
    this.load(template);
    }
    
Template.prototype.constructor = Template;

Template.prototype = {

    template: "",            //Data of the template
    parent: null,              //Parent template
    _data: {},                //Data (only if parent template)
    subTemplates:  [],
    
    //sets template data and loads
    load: function(template) {
        if (!template)
            return;
            
        var RegPattern = /<< BEGIN (#[a-zA-Z0-9_\-]+) >>([\s\S]*)<< END \1 >>/;
        
        this.template = template;
        this.subTemplates = new Array();
        
        //load sub templates
        var newTemplate;
        
        var match = this.template.match(RegPattern);
        
        while (match)
        {
            newTemplate = new Template();
            newTemplate.parent = this;
            newTemplate.load(match[2]);
            this.template = this.template.replace(match[0],"<< " + match[1] + " >>");
            var dataName = match[1].substr(1);
            this.subTemplates[dataName] = newTemplate;
            
            match = this.template.match(RegPattern);
        }
        
        //load data into subTemplates
        this._passDataToSubTemplates();
        
    },
    
    getData: function() 
    {   //gets Data for this template
        var data = this._data.clone();
        var temp_val = null;
        for (var subT in this.subTemplates)
        {
            if (typeof(this.subTemplates[subT]) == 'function')
                continue;
                
            if (isArray(this.subTemplates[subT]))
            {
                var temp_val = new Array();
                for (var i =0; i< this.subTemplates[subT].length; i++)
                    temp_val.push(this.subTemplates[subT][i].getData());
            }
            else
                temp_val = this.subTemplates[subT].getData();
                
            data[subT] = temp_val;
        }
    },
    
    _passDataToSubTemplates: function()
    {   //moves data to subTemplates
        for (var subT in this.subTemplates)
        {
            if (typeof(this.subTemplates[subT]) == 'function')
                continue;
                
            //ignore arrays
            if (isArray(this.subTemplates[subT]))
                continue;
            
            if (!(this._data[subT] == undefined))
            {
                if (isArray(this._data[subT]))
                {   //clone template to match array
                    var template = this.subTemplates[subT];
                    this.subTemplates[subT] = new Array();
                    for(var i=0;i<this._data[subT].length;i++)
                    {
                        this.subTemplates[subT][i] = template.clone();
                        this.subTemplates[subT][i].addData(this._data[subT][i]);
                    }
                }
                else
                {
                    this.subTemplates[subT].addData(this._data[subT]);
                }
                delete this._data[subT];
            }
        }
    },
    
    setData: function(data) 
    {   //replaces data for this template with values
        this._data = {};
        this.addData(data);
    },
    
    addData: function(data)
    {
        for (var dataName in data)
        {
            if (typeof(data[dataName]) == 'function')
                continue;
                
            if (data[dataName] instanceof Template)
                this.addSubTemplate(dataName,data[dataName]);
            else    
                this._data[dataName] = data[dataName];
        }
        this._passDataToSubTemplates();
    },
    
    removeSubTemplate: function(name,removeData)
	{	//removes a sub template, removeData defaults to true
        if (removeData === undefined) var removeData = true;
        
		if (this.subTemplates[name] == undefined)
		{
			if (DEBUG && typeof(console != undefined)) console.error('Unknown SubTemplate: ' + name);
			return null;
		}
		
		var template = this.subTemplates[name];
        
        if (!removeData) this._data[name] = template.getData();
        
		delete this.subTemplates[name];
		var newDataSet = {};
		newDataSet[name] = '';
		this.addData(newDataSet );	//adds an empty data set to cover template entry
		return template;
	},
    
    
    addSubTemplate: function(dataName,subTemplate) 
    {   //adds a sub template from data loaded externally, must a string or a template
        
        if (typeof(subTemplate) == 'string')
        {
            var template = new Template();
            subTemplate = template.load(subTemplate);
        }
        
        if (!isArray(subTemplate))
            subTemplate.parent = this;
        
        this.subTemplates[dataName] = subTemplate;
		
        this._passDataToSubTemplates();
    },
        
    parse: function(data) 
    {   //Parses this template, data is optional override
        if (data == undefined) var data = this._data;
        
        var output = this.template;
            
        //iterate over subtemplates first
        //we replace subtemplates first because this allows cascading of data values (so data from the top level can replace into lower levels)
        for (var subT in this.subTemplates)
        {
            if (typeof(this.subTemplates[subT]) == 'function')
                continue;
                
            var subTemplate = this.subTemplates[subT];
            var subOutput = '';
            
			
            //if we didnt find the sub template then ignore
            if (subTemplate == null)
                continue;
                    
                if (isArray(subTemplate))
                {
                    if (subT == 'channelrow')
                        var test = true;
                    //For arrays call subtemplate for each data set
                    var i =0;
                    for (i = 0;i<subTemplate.length;i++)
                    {
                        var test_output = subTemplate[i].parse();
                        subOutput = subOutput + test_output;
                        //subOutput = subOutput + subTemplate.parseWithData(data[datumName][i]); TODO: REMOVE
                    }
                }
                else
                {
                    //Not an array so just fill data
                    subOutput = subTemplate.parse();
                }

            output = output.replace('<< #' + subT + ' >>',subOutput);
        }
        
        //now iterate data values from this level
        for (var datumName in data)
        {
            if (typeof(data[datumName]) == 'function')
                continue;
                
            var type = typeof(data[datumName]);

            //replace all occurences
            var regex = new RegExp('<< #' + datumName + ' >>','g');
            output = output.replace(regex,data[datumName]);
            output = output;
        }
        
        return output;
    },
	clone: function()
	{
		var tpl = new Template();
		tpl.template = this.template;
		
		//Clone data
		var newdata = {};
		for (var i in this._data) newdata[i] = this._data[i];
		tpl._data = newdata;
		
        tpl.subTemplates = new Array();
        //Clone child templates
        for (var subT in this.subTemplates)
        {
            if (typeof(this.subTemplates[subT]) == 'function')
                continue;
                
            tpl.addSubTemplate(subT,this.subTemplates[subT].clone());
        }
        
		return tpl;
	}
}
    
    //Helper function to determine if an object is an array (has only numeric properties)
    //Attribution: Devin, http://www.devinolson.net/devin/SpankysPlace.nsf/d6plinks/DOLN-7RTGYY
   function isArray(obj) {
   if (!obj) { return false; }
   try {
     if (!(obj.propertyIsEnumerable("length"))
       && (typeof obj === "object")
       && (typeof obj.length === "number")) {
         for (var idx in obj) {
           if (!isNumeric(idx) && typeof obj[idx] != "function") { 
           return false; 
           }
         } // for (var idx in object)
         return true;
     } else {
       return false;
     } // if (!(obj.propertyIsEnumerable("length"))...
   } catch (e) {
     return false;
   } // try
} // isArray() 

function isNumeric(obj) {
   try {
     return (((obj - 0) == obj) && (obj.length > 0));
   } catch (e) {
     return false;
   } // try
} // isNumeric() 
